﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace Leow.TextEditor
{
    /// <summary>
    /// An enhanced <c>TextBox</c> that can have a line number margin, multi-
    /// colored texts and highlights, horizontal lines between text lines, as
    /// well as line-based text selection.
    /// </summary>
    public class TextEditor : TextBox
    {
        #region Constants

        private const string BrushesCategoryString = "Brushes";
        private const string CommonCategoryString = "Common";
        private const string LineNumberMarginCategoryString = "Line Number Margin";

        #endregion

        #region Dependency properties

        /// <summary>
        /// Identifies the Background property.
        /// </summary>
        public new static readonly DependencyProperty BackgroundProperty
            = DependencyProperty.Register(
                "Background",
                typeof(Brush),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    SystemColors.ControlLightLightBrush,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    BackgroundChangedCallback));

        /// <summary>
        /// Identifies the CaretBrush property.
        /// </summary>
        public new static readonly DependencyProperty CaretBrushProperty
            = DependencyProperty.Register(
                "CaretBrush",
                typeof(Brush),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(CaretBrushChangedCallback));

        /// <summary>
        /// Identifies the FontFamily property.
        /// </summary>
        public new static readonly DependencyProperty FontFamilyProperty
            = DependencyProperty.Register(
                "FontFamily",
                typeof(FontFamily),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    Control.FontFamilyProperty.DefaultMetadata.DefaultValue,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    FontFamilyChangedCallback));

        /// <summary>
        /// Identifies the Foreground property.
        /// </summary>
        public new static readonly DependencyProperty ForegroundProperty
            = DependencyProperty.Register(
                "Foreground",
                typeof(Brush),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    Control.ForegroundProperty.DefaultMetadata.DefaultValue,
                    FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// Identifies the HorizontalLinesBrush property.
        /// </summary>
        public static readonly DependencyProperty HorizontalLinesBrushProperty
            = DependencyProperty.Register(
                "HorizontalLinesBrush",
                typeof(Brush),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    null,
                    FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// Identifies the IsEnabled property.
        /// </summary>
        public static new readonly DependencyProperty IsEnabledProperty
            = DependencyProperty.Register(
                "IsEnabled",
                typeof(bool),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    true,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    IsEnabledChangedCallback));

        /// <summary>
        /// Identifies the IsLineNumberMarginShown property.
        /// </summary>
        public static readonly DependencyProperty IsLineNumberMarginShownProperty
            = DependencyProperty.Register(
                "IsLineNumberMarginShown",
                typeof(bool),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    true,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    IsLineNumberMarginShownChangedCallback));

        /// <summary>
        /// Identifies the LineNumberMarginBackground property.
        /// </summary>
        public static readonly DependencyProperty LineNumberMarginBackgroundProperty
            = DependencyProperty.Register(
                "LineNumberMarginBackground",
                typeof(Brush),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    Brushes.Transparent,
                    FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// Identifies the LineNumberMarginForeground property.
        /// </summary>
        public static readonly DependencyProperty LineNumberMarginForegroundProperty
            = DependencyProperty.Register(
                "LineNumberMarginForeground",
                typeof(Brush),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    new SolidColorBrush(Color.FromRgb(43, 145, 175)),
                    FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// Identifies the LineNumberMarginPadding property.
        /// </summary>
        public static readonly DependencyProperty LineNumberMarginPaddingProperty
            = DependencyProperty.Register(
                "LineNumberMarginPadding",
                typeof(Thickness),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    new Thickness(10),
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    LineNumberMarginPaddingChangedCallback));

        /// <summary>
        /// Identifies the LineNumberMarginStartingNumber property.
        /// </summary>
        public static readonly DependencyProperty LineNumberMarginStartingNumberProperty
            = DependencyProperty.Register(
                "LineNumberMarginStartingNumber",
                typeof(int),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    1,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    LineNumberMarginStartingNumberChangedCallback));

        /// <summary>
        /// Identifies the SelectedLineCount property.
        /// </summary>
        public static readonly DependencyProperty SelectedLineCountProperty
            = DependencyProperty.Register(
                "SelectedLineCount",
                typeof(int),
                typeof(TextEditor),
                new PropertyMetadata(SelectedLineCountChangedCallback));

        /// <summary>
        /// Identifies the SelectedLineIndex property.
        /// </summary>
        public static readonly DependencyProperty SelectedLineIndexProperty
            = DependencyProperty.Register(
                "SelectedLineIndex",
                typeof(int),
                typeof(TextEditor),
                new PropertyMetadata(SelectedLineIndexChangedCallback));

        /// <summary>
        /// Identifies the SelectionBrush property.
        /// </summary>
        public static new readonly DependencyProperty SelectionBrushProperty
            = DependencyProperty.Register(
                "SelectionBrush",
                typeof(Brush),
                typeof(TextEditor),
                new PropertyMetadata(
                    TextBoxBase.SelectionBrushProperty.DefaultMetadata.DefaultValue,
                    SelectionBrushChangedCallback));

        /// <summary>
        /// Identifies the SelectionMode property.
        /// </summary>
        public static readonly DependencyProperty SelectionModeProperty
            = DependencyProperty.Register(
                "SelectionMode",
                typeof(TextEditorSelectionMode),
                typeof(TextEditor),
                new PropertyMetadata(
                    TextEditorSelectionMode.Character,
                    SelectionModeChangedCallback));

        /// <summary>
        /// Identifies the SeparatorBrush property.
        /// </summary>
        public static readonly DependencyProperty SeparatorBrushProperty
            = DependencyProperty.Register(
                "SeparatorBrush",
                typeof(Brush),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    SystemColors.ControlDarkBrush,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    SeparatorBrushChangedCallback));

        /// <summary>
        /// Identifies the SeparatorWidth property.
        /// </summary>
        public static readonly DependencyProperty SeparatorWidthProperty
            = DependencyProperty.Register(
                "SeparatorWidth",
                typeof(double),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    1.0,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    SeparatorWidthChangedCallback));

        /// <summary>
        /// Identifies the TextFormats property.
        /// </summary>
        public static readonly DependencyProperty TextFormatsProperty
            = DependencyProperty.Register(
                "TextFormats",
                typeof(IList<ITextFormat>),
                typeof(TextEditor),
                new FrameworkPropertyMetadata(
                    new List<ITextFormat>(),
                    FrameworkPropertyMetadataOptions.AffectsRender));

        #endregion

        #region Fields

        private Rect _backgroundRect;
        private RectangleGeometry _foregroundClip;
        private bool _isFirstTimeRender = true;
        private bool _isSelectionChangedCallbackIgnored;
        private bool _isSelectionChangedInCode;
        private LineNumberMargin _lineNumberMargin;
        private List<string> _lineTexts;
        private int _previousSelectionStart;
        private int _previousSelectionLength;
        private ScrollContentPresenter _scrollContentPresenter;
        private int _selectionLengthBeforeRightClick;
        private Rect _selectionRect;
        private int _selectionStartBeforeRightClick;

        #endregion

        #region Constructors

        static TextEditor()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(TextEditor),
                new FrameworkPropertyMetadata(typeof(TextEditor)));
        }

        /// <summary>
        /// Initializes a new instance of the <c>TextEditor</c> class.
        /// </summary>
        public TextEditor()
        {
            AcceptsReturn = true;
            AcceptsTab = true;
            HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
            VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
            TextWrapping = TextWrapping.NoWrap;

            base.Background = null;
            base.Foreground = null;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a brush that describes the background of the
        /// <c>TextEditor</c>.
        /// </summary>
        public new Brush Background
        {
            get
            {
                return GetValue(BackgroundProperty) as Brush;
            }
            set
            {
                SetValue(BackgroundProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the brush that is used to paint the caret of the
        /// <c>TextEditor</c>.
        /// </summary>
        public new Brush CaretBrush
        {
            get
            {
                return GetValue(CaretBrushProperty) as Brush;
            }
            set
            {
                SetValue(CaretBrushProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the font family of the <c>TextEditor</c>.
        /// </summary>
        public new FontFamily FontFamily
        {
            get
            {
                return GetValue(FontFamilyProperty) as FontFamily;
            }
            set
            {
                SetValue(FontFamilyProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a brush that describes the foreground color.
        /// </summary>
        public new Brush Foreground
        {
            get
            {
                return GetValue(ForegroundProperty) as Brush;
            }
            set
            {
                SetValue(ForegroundProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the brush that is used to draw the horizontal lines in
        /// the <c>TextEditor</c>. 
        /// </summary>
        [Category(BrushesCategoryString)]
        public Brush HorizontalLinesBrush
        {
            get
            {
                return GetValue(HorizontalLinesBrushProperty) as Brush;
            }
            set
            {
                SetValue(HorizontalLinesBrushProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this element is enabled in
        /// the user interface (UI).
        /// </summary>
        public new bool IsEnabled
        {
            get
            {
                return (bool)GetValue(IsEnabledProperty);
            }
            set
            {
                SetValue(IsEnabledProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a value that declares whether the line number margin
        /// is shown.
        /// </summary>
        [Category(LineNumberMarginCategoryString)]
        public bool IsLineNumberMarginShown
        {
            get
            {
                return (bool)GetValue(IsLineNumberMarginShownProperty);
            }
            set
            {
                SetValue(IsLineNumberMarginShownProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a brush that describes the background of the line
        /// number margin.
        /// </summary>
        [Category(LineNumberMarginCategoryString)]
        public Brush LineNumberMarginBackground
        {
            get
            {
                return GetValue(LineNumberMarginBackgroundProperty) as Brush;
            }
            set
            {
                SetValue(LineNumberMarginBackgroundProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a brush that describes the foreground color of the line
        /// number margin.
        /// </summary>
        [Category(LineNumberMarginCategoryString)]
        public Brush LineNumberMarginForeground
        {
            get
            {
                return GetValue(LineNumberMarginForegroundProperty) as Brush;
            }
            set
            {
                SetValue(LineNumberMarginForegroundProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the padding inside the line number margin.
        /// </summary>
        [Category(LineNumberMarginCategoryString)]
        public Thickness LineNumberMarginPadding
        {
            get
            {
                return (Thickness)GetValue(LineNumberMarginPaddingProperty);
            }
            set
            {
                SetValue(LineNumberMarginPaddingProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the starting line number.
        /// </summary>
        [Category(LineNumberMarginCategoryString)]
        public int LineNumberMarginStartingNumber
        {
            get
            {
                return (int)GetValue(LineNumberMarginStartingNumberProperty);
            }
            set
            {
                SetValue(LineNumberMarginStartingNumberProperty, value);
            }
        }

        /// <summary>
        /// Gets the lines of text.
        /// </summary>
        [Category(CommonCategoryString)]
        public List<string> LineTexts
        {
            get
            {
                return _lineTexts ??
                       (_lineTexts = new List<string> { string.Empty });
            }
        }

        /// <summary>
        /// Gets or sets the number of lines that are being selected.
        /// </summary>
        public int SelectedLineCount
        {
            get
            {
                return (int)GetValue(SelectedLineCountProperty);
            }
            set
            {
                SetValue(SelectedLineCountProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the starting line index of the lines that are being
        /// selected.
        /// </summary>
        public int SelectedLineIndex
        {
            get
            {
                return (int)GetValue(SelectedLineIndexProperty);
            }
            set
            {
                SetValue(SelectedLineIndexProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the brush that highlights selected text.
        /// </summary>
        public new Brush SelectionBrush
        {
            get
            {
                return GetValue(SelectionBrushProperty) as Brush;
            }
            set
            {
                SetValue(SelectionBrushProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a value that describes the mode of selection.
        /// </summary>
        public TextEditorSelectionMode SelectionMode
        {
            get
            {
                return (TextEditorSelectionMode)GetValue(SelectionModeProperty);
            }
            set
            {
                SetValue(SelectionModeProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a brush that is used to paint the separator between
        /// the line number margin and the text content area.
        /// </summary>
        [Category(LineNumberMarginCategoryString)]
        public Brush SeparatorBrush
        {
            get
            {
                return GetValue(SeparatorBrushProperty) as Brush;
            }
            set
            {
                SetValue(SeparatorBrushProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the width of the separator between the line number
        /// margin and the text content area.
        /// </summary>
        [Category(LineNumberMarginCategoryString)]
        public double SeparatorWidth
        {
            get
            {
                return (double)GetValue(SeparatorWidthProperty);
            }
            set
            {
                SetValue(SeparatorWidthProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the list of text formatting instructions to be applied
        /// to the renderer of the <c>TextEditor</c>.
        /// </summary>
        public IList<ITextFormat> TextFormats
        {
            get
            {
                return GetValue(TextFormatsProperty) as List<ITextFormat>;
            }
            set
            {
                SetValue(TextFormatsProperty, value);
            }
        }

        #endregion

        #region Overridden methods

        /// <summary>
        /// When overridden in a derived class, participates in rendering
        /// operations that are directed by the layout system. The rendering
        /// instructions for this element are not used directly when this method
        /// is invoked, and are instead preserved for later asynchronous use by
        /// layout and drawing. (Inherited from UIElement.)
        /// </summary>
        /// <param name="drawingContext">The drawing instructions for a specific
        /// element. This context is provided to the layout system.</param>
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            if (LineCount == 0)
            {
                Dispatcher.BeginInvoke(
                    new Action(InvalidateVisual),
                    DispatcherPriority.Render);
                return;
            }

            if (_isFirstTimeRender)
            {
                InitializeVisualComponents();
                UpdateDrawingParams();
                _isFirstTimeRender = false;
            }

            // Render the background rectangle
            drawingContext.DrawRectangle(
                IsEnabled ? Background : SystemColors.ControlBrush,
                null,
                _backgroundRect);

            // Get the portion of the text that is visible to the user
            var tuple = GetVisibleText();
            string visibleText = tuple.Item1;
            int firstVisibleLineIndex = tuple.Item2;
            int lastVisibleLineIndex = tuple.Item3;
            int firstVisibleCharIndex = tuple.Item4;

            Point firstVisibleCharLeadingEdge
                = GetRectFromCharacterIndex(firstVisibleCharIndex).TopLeft;
            Point firstVisibleCharTrailingEdge
                = GetRectFromCharacterIndex(firstVisibleCharIndex, true).BottomRight;
            double lineHeight
                = firstVisibleCharTrailingEdge.Y - firstVisibleCharLeadingEdge.Y;

            // Render the line number margin
            if (_lineNumberMargin != null)
            {
                _lineNumberMargin.Render(
                    drawingContext,
                    firstVisibleLineIndex,
                    lastVisibleLineIndex,
                    firstVisibleCharLeadingEdge);
            }

            // Render the text
            drawingContext.PushClip(_foregroundClip);
            var formattedText = new FormattedText(
                visibleText,
                CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight,
                new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
                FontSize,
                IsEnabled ? Foreground : SystemColors.GrayTextBrush,
                null,
                TextOptions.GetTextFormattingMode(this))
            {
                Trimming = TextTrimming.None
            };
            ApplyTextFormats(
                drawingContext,
                formattedText,
                firstVisibleLineIndex,
                lastVisibleLineIndex,
                firstVisibleCharIndex,
                firstVisibleCharLeadingEdge,
                lineHeight);
            drawingContext.DrawText(formattedText, firstVisibleCharLeadingEdge);

            // Render the selection rectangle
            if (SelectionMode == TextEditorSelectionMode.Line
                && SelectionLength != 0)
            {
                double selectionTop
                    = firstVisibleCharLeadingEdge.Y
                      + lineHeight * (SelectedLineIndex - firstVisibleLineIndex);

                int lineHeightMultiplier = Math.Min(
                    SelectedLineCount,
                    lastVisibleLineIndex - SelectedLineIndex + 1);
                lineHeightMultiplier = Math.Max(lineHeightMultiplier, 0);

                double selectionHeight = lineHeight * lineHeightMultiplier;
                _selectionRect = new Rect(
                    _foregroundClip.Rect.Left,
                    selectionTop,
                    _foregroundClip.Rect.Width,
                    selectionHeight);

                drawingContext.PushOpacity(SelectionOpacity);
                drawingContext.DrawRectangle(SelectionBrush, null, _selectionRect);
                drawingContext.Pop();
            }

            drawingContext.Pop();

            // Render the horizontal lines
            if (HorizontalLinesBrush != null
                && HorizontalLinesBrush != Brushes.Transparent)
            {
                var rect = new Rect(
                    _backgroundRect.Left,
                    _foregroundClip.Rect.Top,
                    _backgroundRect.Right,
                    _foregroundClip.Rect.Height);
                drawingContext.PushClip(new RectangleGeometry(rect));

                for (int i = firstVisibleLineIndex; i <= lastVisibleLineIndex; i++)
                {
                    double y
                        = firstVisibleCharTrailingEdge.Y
                          + lineHeight * (i - firstVisibleLineIndex);
                    y = Math.Floor(y) + 0.5;
                    drawingContext.DrawLine(
                        new Pen(HorizontalLinesBrush, 1),
                        new Point(0, y),
                        new Point(ActualWidth, y));
                }
            }
        }

        /// <summary>
        /// Is called when the caret or current selection changes position.
        /// </summary>
        /// <param name="e">The arguments that are associated with the
        /// SelectionChanged event.</param>
        protected override void OnSelectionChanged(RoutedEventArgs e)
        {
            base.OnSelectionChanged(e);

            if (SelectionStart != _previousSelectionStart
                || SelectionLength != _previousSelectionLength)
            {
                _previousSelectionStart = SelectionStart;
                _previousSelectionLength = SelectionLength;

                if (!_isSelectionChangedInCode)
                {
                    _isSelectionChangedCallbackIgnored = true;

                    int firstSelectedLineIndex
                        = GetLineIndexFromCharacterIndex(SelectionStart);
                    SelectedLineIndex = firstSelectedLineIndex;

                    if (SelectionLength == 0)
                    {
                        SelectedLineCount = 0;
                    }
                    else
                    {
                        int selectionEndMinusNewLine
                            = SelectionStart + SelectionLength;
                        if (SelectedText.EndsWith("\r\n"))
                        {
                            selectionEndMinusNewLine -= 2;
                        }
                        else if (SelectedText.EndsWith("\n")
                                 || SelectedText.EndsWith("\r"))
                        {
                            selectionEndMinusNewLine -= 1;
                        }

                        int lastSelectedLineIndex
                            = GetLineIndexFromCharacterIndex(selectionEndMinusNewLine);
                        SelectedLineCount
                            = lastSelectedLineIndex - firstSelectedLineIndex + 1;
                    }

                    _isSelectionChangedCallbackIgnored = false;
                }

                InvalidateVisual();
                UpdateLayout();
            }
        }

        /// <summary>
        /// Is called when content in this editing control changes.
        /// </summary>
        /// <param name="e">The arguments that are associated with the
        /// TextChanged event.</param>
        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            base.OnTextChanged(e);

            UpdateLineTexts(e.Changes.First());

            if (_lineNumberMargin != null)
            {
                _lineNumberMargin.UpdateWidth();
            }

            UpdateLayout();
            InvalidateVisual();
        }

        #endregion

        #region Property changed callbacks

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the Background dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void BackgroundChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            var newValue = e.NewValue as SolidColorBrush;
            if (textEditor != null && textEditor.CaretBrush == null)
            {
                textEditor.SetBaseCaretBrush(new SolidColorBrush(
                                                 newValue == null
                                                     ? Colors.Black
                                                     : newValue.Color.Invert()));
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the CaretBrush dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void CaretBrushChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            var newValue = e.NewValue as Brush;
            if (textEditor != null)
            {
                if (newValue == null)
                {
                    var background = textEditor.Background as SolidColorBrush;
                    textEditor.SetBaseCaretBrush(new SolidColorBrush(
                                                     background == null
                                                         ? Colors.Black
                                                         : background.Color.Invert()));
                }
                else
                {
                    textEditor.SetBaseCaretBrush(newValue);
                }
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the FontFamily dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void FontFamilyChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null)
            {
                textEditor.EnsureFontFamilyIsProperlyAssigned();
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the IsEnabled dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void IsEnabledChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null)
            {
                textEditor.SetBaseIsEnabled((bool)e.NewValue);
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the IsLineNumberMarginShown dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void IsLineNumberMarginShownChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null)
            {
                var newValue = (bool)e.NewValue;
                if (newValue && textEditor._lineNumberMargin == null)
                {
                    textEditor._lineNumberMargin
                        = new LineNumberMargin(textEditor);
                    textEditor.UpdateDrawingParams();
                    textEditor.InvalidateVisual();
                }
                else if (!newValue && textEditor._lineNumberMargin != null)
                {
                    textEditor._lineNumberMargin.Close();
                    textEditor._lineNumberMargin = null;
                }
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the LineNumberMarginPadding dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void LineNumberMarginPaddingChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null && textEditor._lineNumberMargin != null)
            {
                textEditor._lineNumberMargin.UpdateWidth(isUseCoercion: true);
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the LineNumberMarginStartingNumber dependency property
        /// changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void LineNumberMarginStartingNumberChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null && textEditor._lineNumberMargin != null)
            {
                textEditor._lineNumberMargin.UpdateWidth(
                    isCalculateFromScratch: true);
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the SelectedLineCount dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void SelectedLineCountChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null
                && !textEditor._isSelectionChangedCallbackIgnored)
            {
                textEditor.SelectLine(
                    textEditor.SelectedLineIndex,
                    (int)e.NewValue);
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the SelectedLineIndex dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void SelectedLineIndexChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null
                && !textEditor._isSelectionChangedCallbackIgnored)
            {
                textEditor.SelectLine(
                    (int)e.NewValue,
                    textEditor.SelectedLineCount);
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the SelectionBrush dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void SelectionBrushChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null
                && textEditor.SelectionMode != TextEditorSelectionMode.Line)
            {
                textEditor.SetBaseSelectionBrush(e.NewValue as Brush);
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the SelectionMode dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void SelectionModeChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null)
            {
                var selectionMode = (TextEditorSelectionMode)e.NewValue;
                if (selectionMode == TextEditorSelectionMode.Line)
                {
                    textEditor.SetBaseSelectionBrush(null);

                    textEditor.PreviewKeyDown
                        += textEditor.PreviewKeyDownEventHandler;
                    textEditor.PreviewMouseRightButtonDown
                        += textEditor.PreviewMouseRightButtonDownEventHandler;
                    textEditor.ContextMenuOpening
                        += textEditor.ContextMenuOpeningEventHandler;
                }
                else
                {
                    textEditor.SetBaseSelectionBrush(textEditor.SelectionBrush);

                    textEditor.PreviewKeyDown
                        -= textEditor.PreviewKeyDownEventHandler;
                    textEditor.PreviewMouseRightButtonDown
                        -= textEditor.PreviewMouseRightButtonDownEventHandler;
                    textEditor.ContextMenuOpening
                        -= textEditor.ContextMenuOpeningEventHandler;
                }
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the SeparatorBrush dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void SeparatorBrushChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null && textEditor._lineNumberMargin != null)
            {
                textEditor._lineNumberMargin.SeparatorPen.Brush
                    = e.NewValue as Brush;
            }
        }

        /// <summary>
        /// Represents the callback that is invoked when the effective property
        /// value of the SeparatorWidth dependency property changes.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by the property changed
        /// event that tracks changes to the effective value of this property.
        /// </param>
        private static void SeparatorWidthChangedCallback(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var textEditor = d as TextEditor;
            if (textEditor != null && textEditor._lineNumberMargin != null)
            {
                textEditor._lineNumberMargin.SeparatorPen.Thickness
                    = (double)e.NewValue;
                textEditor._lineNumberMargin.UpdateWidth(isUseCoercion: true);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Programmatically selects the specified lines.
        /// </summary>
        /// <param name="index">The starting index of the lines to be selected.
        /// </param>
        /// <param name="count">The number of lines to be selected.</param>
        public void SelectLine(int index, int count)
        {
            int start = LineTexts
                .Take(index)
                .Sum(s => s.Length);
            int length = LineTexts
                .Skip(index)
                .Take(count)
                .Sum(s => s.Length);

            _isSelectionChangedInCode = true;
            Select(start, length);
            _isSelectionChangedInCode = false;
        }

        /// <summary>
        /// Applies the specified text formatting to the renderer.
        /// </summary>
        /// <param name="drawingContext">The drawing instructions for a specific
        /// element. This context is provided to the layout system.</param>
        /// <param name="formattedText">The low-level control for drawing text.
        /// </param>
        /// <param name="firstVisibleLineIndex">The index of the first visible
        /// text line.</param>
        /// <param name="lastVisibleLineIndex">The index of the last visible
        /// text line.</param>
        /// <param name="firstVisibleCharIndex">The index of the first visible
        /// character.</param>
        /// <param name="firstVisibleCharLeadingEdge">Location of the leading
        /// edge of the first visible character.</param>
        /// <param name="lineHeight">The height of one text line.</param>
        private void ApplyTextFormats(
            DrawingContext drawingContext,
            FormattedText formattedText,
            int firstVisibleLineIndex,
            int lastVisibleLineIndex,
            int firstVisibleCharIndex,
            Point firstVisibleCharLeadingEdge,
            double lineHeight)
        {
            foreach (ITextFormat tf in TextFormats)
            {
                if (tf is CharacterBasedTextFormat)
                {
                    var textFormat = (CharacterBasedTextFormat)tf;

                    int adjustedStartIndex
                        = textFormat.CharStartIndex - firstVisibleCharIndex;
                    int adjustedCount = textFormat.Count;

                    if (adjustedStartIndex + adjustedCount < 0
                        || adjustedStartIndex >= formattedText.Text.Length)
                    {
                        continue;
                    }

                    if (adjustedStartIndex < 0)
                    {
                        adjustedCount -= 0 - adjustedStartIndex;
                        adjustedStartIndex = 0;
                    }

                    int endIndex = adjustedStartIndex + adjustedCount;
                    if (endIndex >= formattedText.Text.Length)
                    {
                        adjustedCount -= endIndex - formattedText.Text.Length;
                    }

                    if (adjustedCount == 0)
                    {
                        continue;
                    }

                    if (textFormat.Mode == TextFormatMode.Foreground)
                    {
                        formattedText.SetForegroundBrush(
                            textFormat.Brush,
                            adjustedStartIndex,
                            adjustedCount);
                    }
                    else if (textFormat.Mode == TextFormatMode.Highlight)
                    {
                        throw new NotImplementedException(
                            "Highlight mode for character based text formatting is not yet supported.");
                        //var geom = new PathGeometry();
                        //int adjustedEndIndex = adjustedStartIndex + adjustedCount;
                        //int accumulatedCharLength = firstVisibleCharIndex;
                        //for (int i = firstVisibleLineIndex; i <= lastVisibleLineIndex; i++)
                        //{
                        //    if (adjustedStartIndex + adjustedCount > 
                        //    accumulatedCharLength += _lineTexts[i].Length;
                        //}
                        //for (int i = adjustedStartIndex; i <= adjustedEndIndex; i++)
                        //{
                        //    Point leadingEdge = GetRectFromCharacterIndex(i).TopLeft;
                        //    Point trailingEdge = GetRectFromCharacterIndex(i, true).BottomRight;
                        //    var rect = new Rect(
                        //        leadingEdge.X,
                        //        leadingEdge.Y,
                        //        trailingEdge.X - leadingEdge.X,
                        //        trailingEdge.Y - leadingEdge.Y);
                        //    geom.AddGeometry(new RectangleGeometry(rect));
                        //}

                        //drawingContext.PushClip(geom);
                        //drawingContext.DrawRectangle(
                        //    textFormat.Brush,
                        //    null,
                        //    geom.GetRenderBounds(null));
                        //drawingContext.Pop();
                    }
                }
                else if (tf is LineBasedTextFormat)
                {
                    var textFormat = (LineBasedTextFormat)tf;

                    int firstLineIndex = textFormat.FirstLineIndex;
                    int lastLineIndex = textFormat.LastLineIndex;
                    if (firstLineIndex > lastVisibleLineIndex
                        || lastLineIndex < firstVisibleLineIndex)
                    {
                        continue;
                    }

                    firstLineIndex
                        = Math.Max(firstLineIndex, firstVisibleLineIndex);
                    lastLineIndex
                        = Math.Min(lastLineIndex, lastVisibleLineIndex);

                    if (textFormat.Mode == TextFormatMode.Foreground)
                    {
                        int adjustedStartIndex = LineTexts
                            .Skip(firstVisibleLineIndex)
                            .Take(firstLineIndex - firstVisibleLineIndex)
                            .Sum(s => s.Length);
                        int adjustedCount = LineTexts
                            .Skip(firstLineIndex)
                            .Take(lastLineIndex - firstLineIndex + 1)
                            .Sum(s => s.Length);
                        formattedText.SetForegroundBrush(
                            textFormat.Brush,
                            adjustedStartIndex,
                            adjustedCount);
                    }
                    else if (textFormat.Mode == TextFormatMode.Highlight)
                    {
                        double top
                            = firstVisibleCharLeadingEdge.Y
                              + lineHeight * (firstLineIndex - firstVisibleLineIndex);
                        double height
                            = lineHeight * (lastLineIndex - firstLineIndex + 1);
                        var rect = new Rect(
                            _foregroundClip.Rect.Left,
                            top,
                            _foregroundClip.Rect.Width,
                            height);
                        drawingContext.DrawRectangle(textFormat.Brush, null, rect);
                    }
                }
            }
        }

        /// <summary>
        /// <para>The system will automatically choose a default font if the
        /// specified font family cannot be found. Somehow, this can lead to
        /// buggy calculation of certain text computation methods such as
        /// <c>TextBox.GetFirstVisibleLineIndex</c> unless the FontFamily
        /// property is also explicitly set to that default font.</para>
        /// <para>This method ensures that this class is aware when a desired
        /// font is not available. It will then find out the fallback font
        /// chosen by the system and set the FontFamily property value to that
        /// fallback font.</para>
        /// </summary>
        private void EnsureFontFamilyIsProperlyAssigned()
        {
            base.FontFamily = FontFamily;
            var specifiedFontNames = base.FontFamily.Source.Split(
                ",".ToArray(),
                StringSplitOptions.RemoveEmptyEntries)
                .Select(s => s.Trim());
            bool isSpecifiedFontsAvailable = Fonts.SystemFontFamilies
                .Any(ff => specifiedFontNames.Contains(ff.Source));
            if (!isSpecifiedFontsAvailable
                && base.FontFamily.FamilyNames.Values != null)
            {
                string defaultFallbackFontName = base.FontFamily.FamilyNames.Values
                    .First();
                FontFamily = new FontFamily(defaultFallbackFontName);
            }
        }

        /// <summary>
        /// <para>Estimates and returns the portion of the text that is visible
        /// to the user.</para>
        /// <para>The renderer will then not need to draw the entire text and
        /// this might improve the performance.</para>
        /// </summary>
        /// <returns>A quadruple that consists of, in this order, the visible
        /// text, the first visible line index, the last visible line index, and
        /// the first visible character index.</returns>
        private Tuple<string, int, int, int> GetVisibleText()
        {
            // GetFirstVisibleLineIndex() and GetLastVisibleLineIndex() get more
            // inaccurate as the text gets longer. They return line indices that
            // are a little below or over the expected, hence a semi-hardcoded
            // offset has to be added or subtracted
            int offset = LineCount / 10000;

            int firstVisibleLineIndex = GetFirstVisibleLineIndex() - offset;
            if (firstVisibleLineIndex < 0)
            {
                firstVisibleLineIndex = 0;
            }
            else if (firstVisibleLineIndex >= LineCount)
            {
                firstVisibleLineIndex = LineCount - 1;
            }

            int lastVisibleLineIndex = GetLastVisibleLineIndex() + offset;
            if (lastVisibleLineIndex < 0)
            {
                lastVisibleLineIndex = 0;
            }
            else if (lastVisibleLineIndex >= LineCount)
            {
                lastVisibleLineIndex = LineCount - 1;
            }

            // GetCharacterIndexFromLineIndex() was found to be completely
            // unreliable when the text exceeds ~20000 lines. Hence, these
            // deep computations have to be formulated
            int firstVisibleCharIndex = LineTexts
                .Take(firstVisibleLineIndex)
                .Sum(s => s.Length);
            int length = LineTexts
                .Skip(firstVisibleLineIndex)
                .Take(lastVisibleLineIndex - firstVisibleLineIndex + 1)
                .Sum(s => s.Length);

            string visibleText = Text.Substring(firstVisibleCharIndex, length);

            return Tuple.Create(
                visibleText,
                firstVisibleLineIndex,
                lastVisibleLineIndex,
                firstVisibleCharIndex);
        }

        /// <summary>
        /// Preliminary initialization instructions that must be performed
        /// before the first rendering pass.
        /// </summary>
        private void InitializeVisualComponents()
        {
            var scrollViewer = this
                .GetFirstVisualChildOfType<ScrollViewer>();
            scrollViewer.ScrollChanged += ScrollChangedEventHandler;

            _scrollContentPresenter = scrollViewer
                .GetFirstVisualChildOfType<ScrollContentPresenter>();

            if (IsLineNumberMarginShown && _lineNumberMargin == null)
            {
                _lineNumberMargin = new LineNumberMargin(this);
            }
        }

        /// <summary>
        /// Stores the selection details in class fields for later use.
        /// </summary>
        private void RememberSelectionPriorToContextMenuOpening()
        {
            _selectionStartBeforeRightClick = SelectionStart;
            _selectionLengthBeforeRightClick = SelectionLength;
        }

        /// <summary>
        /// Sets the CaretBrush property value of the base class.
        /// </summary>
        /// <param name="brush">The brush that is used to paint the caret of the
        /// text box.</param>
        private void SetBaseCaretBrush(Brush brush)
        {
            base.CaretBrush = brush;
        }

        /// <summary>
        /// Sets the IsEnabled property value of the base class.
        /// </summary>
        /// <param name="isEnabled"><c>true</c> if the element is enabled;
        /// otherwise, <c>false</c>.</param>
        private void SetBaseIsEnabled(bool isEnabled)
        {
            base.IsEnabled = isEnabled;
        }

        /// <summary>
        /// Sets the SelectionBrush property value of the base class.
        /// </summary>
        /// <param name="brush">The brush that highlights selected text.</param>
        private void SetBaseSelectionBrush(Brush brush)
        {
            base.SelectionBrush = brush;
        }

        /// <summary>
        /// Recalculate certain drawing parameters that will be used by the
        /// renderer.
        /// </summary>
        private void UpdateDrawingParams()
        {
            double backgroundWidth
                = ActualWidth - BorderThickness.Left - BorderThickness.Right;
            double backgroundHeight
                = ActualHeight - BorderThickness.Top - BorderThickness.Bottom;
            _backgroundRect = new Rect(
                BorderThickness.Left,
                BorderThickness.Top,
                Math.Max(backgroundWidth, 0),
                Math.Max(backgroundHeight, 0));

            _foregroundClip = new RectangleGeometry(
                _scrollContentPresenter.GetBoundsRelativeTo(this));

            if (_lineNumberMargin != null)
            {
                _lineNumberMargin.UpdateDrawingParams(
                    _scrollContentPresenter,
                    _foregroundClip);
            }
        }

        /// <summary>
        /// Updates the class field that keeps track of the texts on a line-by-
        /// line basis. Although this process incurs some overhead, its purpose
        /// is to provide an easy means of retrieval of the text later on, which
        /// can boost the performance considerably.
        /// </summary>
        /// <param name="textChange">Contains information about the changes
        /// made to the text of this control.</param>
        private void UpdateLineTexts(TextChange textChange)
        {
            // Update _lineText when there are characters removed from the text
            if (textChange.RemovedLength != 0)
            {
                int count = LineTexts.Count;
                int firstCharIndex = 0;
                for (int i = 0; i < count; i++)
                {
                    string currentLine = LineTexts[i];
                    if (firstCharIndex + currentLine.Length > textChange.Offset)
                    {
                        int currentLineOffset
                            = textChange.Offset - firstCharIndex;
                        int remainingLengthToBeRemoved
                            = textChange.RemovedLength
                            - (currentLine.Length - currentLineOffset);
                        if (remainingLengthToBeRemoved < 0)
                        {
                            LineTexts[i] = currentLine.Remove(
                                currentLineOffset,
                                textChange.RemovedLength);
                        }
                        else
                        {
                            string newText = currentLine.Remove(currentLineOffset);

                            int lineIndex = i + 1;
                            while (lineIndex < LineTexts.Count
                                && remainingLengthToBeRemoved >= LineTexts[lineIndex].Length)
                            {
                                remainingLengthToBeRemoved -= LineTexts[lineIndex].Length;
                                lineIndex++;
                            }

                            LineTexts.RemoveRange(i + 1, lineIndex - (i + 1));

                            if (i < LineTexts.Count - 1)
                            {
                                newText += LineTexts[i + 1].Substring(remainingLengthToBeRemoved);
                                LineTexts.RemoveAt(i + 1);
                            }

                            LineTexts[i] = newText;
                        }

                        break;
                    }

                    firstCharIndex += currentLine.Length;
                }
            }

            // Update _lineText when there are characters added to the text
            if (textChange.AddedLength != 0)
            {
                string addedText
                    = Text.Substring(textChange.Offset, textChange.AddedLength);
                int count = LineTexts.Count;
                int firstCharIndex = 0;
                for (int i = 0; i < count; i++)
                {
                    string currentLine = LineTexts[i];
                    if (firstCharIndex + currentLine.Length > textChange.Offset)
                    {
                        int currentLineOffset
                            = textChange.Offset - firstCharIndex;
                        string newText;
                        if (currentLineOffset == 0)
                        {
                            newText = addedText + currentLine;
                        }
                        else
                        {
                            newText
                                = currentLine.Remove(currentLineOffset)
                                + addedText
                                + currentLine.Substring(currentLineOffset);
                        }

                        var newLines = newText
                            .ToLines();
                        LineTexts.RemoveAt(i);
                        LineTexts.InsertRange(i, newLines);

                        return;
                    }

                    firstCharIndex += currentLine.Length;
                }

                int lastLineIndex = count - 1;
                string text = LineTexts[lastLineIndex] + addedText;
                var lines = (text + '\0')
                    .ToLines();
                LineTexts.RemoveAt(lastLineIndex);
                LineTexts.AddRange(lines);
                LineTexts[LineTexts.Count - 1]
                    = LineTexts[LineTexts.Count - 1].TrimEnd('\0');
            }
        }

        #endregion

        #region Event handlers

        /// <summary>
        /// Represents the method that will handle the ContextMenuOpening event.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.
        /// </param>
        /// <param name="e">The event data.</param>
        private void ContextMenuOpeningEventHandler(
            object sender,
            ContextMenuEventArgs e)
        {
            Select(_selectionStartBeforeRightClick, _selectionLengthBeforeRightClick);
            ScrollToLine(SelectedLineIndex);
        }

        /// <summary>
        /// Represents the method that will handle the PreviewKeyDown event.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.
        /// </param>
        /// <param name="e">The event data.</param>
        private void PreviewKeyDownEventHandler(
            object sender,
            KeyEventArgs e)
        {
            if (e.Key == Key.Apps)
            {
                RememberSelectionPriorToContextMenuOpening();
            }
        }

        /// <summary>
        /// Represents the method that will handle the
        /// PreviewMouseRightButtonDown event.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.
        /// </param>
        /// <param name="e">The event data.</param>
        private void PreviewMouseRightButtonDownEventHandler(
            object sender,
            MouseButtonEventArgs e)
        {
            RememberSelectionPriorToContextMenuOpening();
        }

        /// <summary>
        /// Represents the method that will handle the ScrollChanged event.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.
        /// </param>
        /// <param name="e">The event data.</param>
        private void ScrollChangedEventHandler(
            object sender,
            ScrollChangedEventArgs e)
        {
            UpdateDrawingParams();
            InvalidateVisual();
        }

        #endregion
    }
}
